package onlinebanking.security.probabilistic;

import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;

import onlinebanking.network.NetworkSwitch;
import onlinebanking.network.SimpleNetworkSwitch;

/**
 * 
 * @author Edgar
 * 
 */
public class GMReinforcedNetworkSwitch implements NetworkSwitch {

	private GMKeyPair gmKeyPair;
	private NetworkSwitch networkSwitch;

	private GMPublicKey otherPartyKey;

	private GMEncoder gmEncoder;
	private GMDecoder gmDecoder;

	public GMReinforcedNetworkSwitch(NetworkSwitch networkSwitch,
			GMKeyPair gmKeyPair) throws IOException {
		this.networkSwitch = networkSwitch;
		this.gmKeyPair = gmKeyPair;

		this.init();
	}

	public GMReinforcedNetworkSwitch(Socket socket) throws IOException {
		GMKeyGenerator gmKeyGenerator = GMKeyGenerator.getInstance();
		this.networkSwitch = new SimpleNetworkSwitch(socket);
		this.gmKeyPair = gmKeyGenerator.generateKeyPair();
		this.init();
	}

	public GMReinforcedNetworkSwitch(NetworkSwitch networkSwitch)
			throws IOException {
		GMKeyGenerator gmKeyGenerator = GMKeyGenerator.getInstance();
		this.networkSwitch = networkSwitch;
		this.gmKeyPair = gmKeyGenerator.generateKeyPair();
		this.init();
	}

	public GMReinforcedNetworkSwitch(Socket socket, GMKeyPair gmKeyPair)
			throws IOException {
		this.networkSwitch = new SimpleNetworkSwitch(socket);
		this.gmKeyPair = gmKeyPair;
		this.init();
	}

	public GMReinforcedNetworkSwitch(Socket socket, GMPrivateKey gmPrivateKey)
			throws IOException {
		GMKeyGenerator gmKeyGenerator = GMKeyGenerator.getInstance();
		this.networkSwitch = new SimpleNetworkSwitch(socket);
		GMPublicKey gmPublicKey = gmKeyGenerator
				.generateGMPublicKeyFromPrivateKey(gmPrivateKey);
		this.gmKeyPair = gmKeyGenerator.wrapToKeyPair(gmPrivateKey, gmPublicKey);
		this.init();
	}

	public GMReinforcedNetworkSwitch(NetworkSwitch networkSwitch,
			GMPrivateKey gmPrivateKey) throws IOException {
		GMKeyGenerator gmKeyGenerator = GMKeyGenerator.getInstance();
		this.networkSwitch = networkSwitch;
		GMPublicKey gmPublicKey = gmKeyGenerator
				.generateGMPublicKeyFromPrivateKey(gmPrivateKey);
		this.gmKeyPair = gmKeyGenerator.wrapToKeyPair(gmPrivateKey, gmPublicKey);
		this.init();
	}

	private void init() throws IOException {
		// do handshake to share public keys
		this.doHandshake();

		// initialize encoder and decoder
		this.gmDecoder = new GMDecoder(this.gmKeyPair.getPrivate());
		this.gmEncoder = new GMEncoder(this.otherPartyKey);
	}

	@Override
	public boolean send(String message) {
		BigInteger[] ciphertextArray = this.gmEncoder.encrypt(message);
		return this.networkSwitch.send(buildCiphertext(ciphertextArray));
	}

	@Override
	public String receive() throws IOException {
		String ciphertextString = this.networkSwitch.receive();
		
		BigInteger[] ciphertextArray = this
				.reconstructCiphertextArray(ciphertextString);
		String message = this.gmDecoder.decrypt(ciphertextArray);
		
//		System.out.println("CIPHERTEXT RECIEVED:"+ ciphertextString);
//		System.out.println("PLAINTEXT RECIEVED:"+ message);
		return message;
	}

	private void doHandshake() throws IOException {

		// send your public key
		String ownGMPublicKeyString = this
				.buildGMPublicKeyString(this.gmKeyPair.getPublic());
		this.networkSwitch.send(ownGMPublicKeyString);

		// get the other party's public key
		String otherPartyGMPublicKeyString = this.networkSwitch.receive();
		this.otherPartyKey = this.parseGMPublicKey(otherPartyGMPublicKeyString);

	}

	private String buildGMPublicKeyString(GMPublicKey gmPublicKey) {
		return gmPublicKey.getModulus().toString() + ','
				+ gmPublicKey.getPseudoSquare().toString();
	}

	private GMPublicKey parseGMPublicKey(String publicKeyString) {
		String[] gmPublicKeyStringArray = publicKeyString.split(",");
		final BigInteger modulus = new BigInteger(gmPublicKeyStringArray[0]);
		final BigInteger pseudoSquare = new BigInteger(
				gmPublicKeyStringArray[1]);
		return new GMPublicKey() {
			@Override
			public BigInteger getModulus() {
				return modulus;
			}

			@Override
			public BigInteger getPseudoSquare() {
				return pseudoSquare;
			}

		};
	}

	private String buildCiphertext(BigInteger[] ciphertextArray) {
		StringBuilder sb = new StringBuilder();
		for (BigInteger bigInteger : ciphertextArray) {
			sb.append(bigInteger).append(',');
		}
		return sb.toString();
	}

	private BigInteger[] reconstructCiphertextArray(String ciphertext) {
		String regex = ",";
		String[] ciphertextStringArray = ciphertext.split(regex);

		int length = ciphertextStringArray.length;

		BigInteger[] ciphertextArray = new BigInteger[length];
		for (int i = 0; i < length; i++) {
			ciphertextArray[i] = new BigInteger(ciphertextStringArray[i]);
		}
		return ciphertextArray;
	}

}
